package com.yfq.springframework.beans.support;

import com.yfq.springframework.beans.*;
import com.yfq.springframework.beans.config.BeanDefinition;
import com.yfq.springframework.beans.config.BeanPostProcessor;
import com.yfq.springframework.beans.config.BeanReference;
import com.yfq.springframework.beans.factory.AutowireCapableBeanFactory;
import com.yfq.springframework.util.BeanUtils;
import com.yfq.springframework.util.ClassUtils;
import com.yfq.springframework.util.StringUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;

/**
 * @author 姚凤桥
 * @date 2023/1/8 17:20
 * @description
 */
public abstract class AbstractAutowiredCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {
    private InstantiationStrategy instantiationStrategy = new SimpleInstantiationStrategy();

    private final ClassLoader classLoader = ClassUtils.getDefaultClassLoader();

    public ClassLoader getClassLoader() {
        return classLoader;
    }

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object... args) {
        Object bean;
        try {
            //1.推断构造方法，创建bean实例
            bean = createBeanInstance(beanName, beanDefinition, args);

            //2.依赖注入
            applyPropertyValues(beanName, bean, beanDefinition);

            //3.执行bean初始化方法和BeanPostProcessor的前置、后置处理方法
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed");
        }

        //4.将destroy方法注册到DefaultSingletonBeanRegistry
        registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);

        //5.将bean注册到容器
        registerSingletonBean(beanName, bean);

        return bean;
    }

    private void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition beanDefinition) {
        if (bean instanceof DisposableBean || !StringUtils.isEmpty(beanDefinition.getDestroyMethodName())) {
            registerDisposableBean(beanName, new DisposableBeanAdapter(beanName, bean, beanDefinition));
        }
    }

    /**
     * 执行初始化方法和BeanPostProcessor方法
     */
    private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition) throws Exception {
        //执行Aware回调
        invokeAwareMethods(beanName, bean);

        //执行postProcessorBeforeInitialization
        Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);

        //执行初始化方法
        invokeInitMethods(beanName, wrappedBean, beanDefinition);

        //执行postProcessorAfterInitialization
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

        return wrappedBean;
    }

    private void invokeAwareMethods(String beanName, Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }

            if (bean instanceof BeanClassLoaderAware) {
                ClassLoader bcl = getClassLoader();
                if (bcl != null) {
                    ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
                }
            }

            if (bean instanceof BeanFactoryAware) {
                ((BeanFactoryAware) bean).setBeanFactory(this);
            }
        }
    }

    private void invokeInitMethods(String beanName, Object bean, BeanDefinition beanDefinition) throws Exception {
        //判断bean是否实现了InitializingBean接口
        if (bean instanceof InitializingBean) {
            InitializingBean initializingBean = (InitializingBean) bean;
            initializingBean.afterPropertiesSet();
        }

        //执行配置的init-method方法
        String initMethodName = beanDefinition.getInitMethodName();
        if (!StringUtils.isEmpty(initMethodName)) {
            Method initMethod = beanDefinition.getBeanClass().getMethod(initMethodName);
            if (initMethod == null) {
                throw new BeansException("Could not find an init method named '" + initMethodName + "' on bean with name '" + beanName + "'");
            }

            initMethod.invoke(bean);
        }
    }


    @Override
    public Object applyBeanPostProcessorsAfterInitialization(Object bean, String beanName) {
        Object resultBean = bean;
        List<BeanPostProcessor> beanPostProcessors = getBeanPostProcessors();
        if (beanPostProcessors == null || beanPostProcessors.isEmpty()) {
            return bean;
        }

        for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
            Object wrappedBean = beanPostProcessor.postProcessAfterInitialization(bean, beanName);
            if (wrappedBean != null) {
                resultBean = wrappedBean;
            }
        }

        return resultBean;
    }

    @Override
    public Object applyBeanPostProcessorsBeforeInitialization(Object bean, String beanName) {
        Object resultBean = bean;
        List<BeanPostProcessor> beanPostProcessors = getBeanPostProcessors();

        if (beanPostProcessors == null || beanPostProcessors.isEmpty()) {
            return bean;
        }

        for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
            Object wrappedBean = beanPostProcessor.postProcessBeforeInitialization(bean, beanName);
            if (wrappedBean != null) {
                resultBean = wrappedBean;
            }
        }

        return resultBean;
    }

    private Object createBeanInstance(String beanName, BeanDefinition beanDefinition, Object... args) {
        Constructor<?> constructorToUse = null;
        Constructor<?>[] constructors = beanDefinition.getBeanClass().getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            if (args != null && constructor.getParameterTypes().length == args.length) {
                constructorToUse = constructor;
                break;
            }
        }

        return instantiationStrategy.instantiate(beanName, beanDefinition, constructorToUse, args);
    }

    /**
     * 依赖注入
     */
    private void applyPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {
        try {
            PropertyValues propertyValues = beanDefinition.getPropertyValues();
            for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
                String name = propertyValue.getName();
                Object value = propertyValue.getValue();
                if (value instanceof BeanReference) {
                    BeanReference beanReference = (BeanReference) value;
                    value = getBean(beanReference.getBeanName());
                }

                BeanUtils.setFieldValue(bean, name, value);
            }
        } catch (Exception e) {
            throw new BeansException("名为[" + beanName + "]的bean依赖注入错误！");
        }
    }

    public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
        this.instantiationStrategy = instantiationStrategy;
    }
}
